package app.core.encrypt;

import cn.dev33.satoken.secure.SaBase64Util;
import cn.dev33.satoken.secure.SaSecureUtil;
import com.gitee.feizns.quickstart.graalvm.Polyglot;
import com.gitee.feizns.quickstart.graalvm.js.Js;
import lombok.RequiredArgsConstructor;
import org.graalvm.polyglot.proxy.ProxyExecutable;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Objects;
import java.util.function.Function;

/**
 * 密码加密策略（plaintext=明文、支持bcrypt、argon2、base64、sha1、sha256、sha384、sha512、md5）
 * @author feizns
 * @since 2025/9/24
 */
@RequiredArgsConstructor
public enum Encoders {

    bcrypt(new BCryptPasswordEncoder()),
    argon2(new Argon2PasswordEncoder()),
    plaintext(new EncodeEqualsPasswordEncoder(Function.identity())),
    base64(new EncodeEqualsPasswordEncoder(SaBase64Util::encode)),
    sha1(new EncodeEqualsPasswordEncoder(SaSecureUtil::sha1)),
    sha256(new EncodeEqualsPasswordEncoder(SaSecureUtil::sha256)),
    sha384(new EncodeEqualsPasswordEncoder(SaSecureUtil::sha384)),
    sha512(new EncodeEqualsPasswordEncoder(SaSecureUtil::sha512)),
    md5(new EncodeEqualsPasswordEncoder(SaSecureUtil::md5));

    /**
     * 密码编码器
     */
    public final PasswordEncoder encoder;

    /**
     * 密码编码器
     * @param type 类型
     * @return {@link PasswordEncoder }
     */
    public static PasswordEncoder of(String type) {
        return valueOf(type).encoder;
    }

    /**
     * js exec 函数
     */
    public static final String JsExecFunction = """
                        function exec(fn, pwd) {
                            fn = eval(fn);
                            if ( typeof fn === 'function' )
                                return fn(pwd);
                            else
                                return fn;
                        }
                    """;

    /**
     * 编码
     * @param type 类型
     * @param password 密码
     * @return {@link String }
     */
    public static Object encode(String type, String password) {
        try( Polyglot js = Js.create()) {
            for (Encoders encoders : Encoders.values()) {
                js.putMember(encoders.name(), (ProxyExecutable) args -> encoders.encoder.encode((args.length >= 1 ? args[0].asString() : password)));
            }
            js.eval(JsExecFunction);
            return js.eval(script(type, password)).asString();
        }
    }

    /**
     * 脚本
     * @param type 类型
     * @param password 密码
     * @return {@link String }
     */
    private static String script(String type, String password) {
        return "exec(\"" + type + "\", '" + password + "');";
    }

    /**
     * @author feizns
     * @since 2025/09/24
     */
    @RequiredArgsConstructor
    public static class EncodeEqualsPasswordEncoder implements PasswordEncoder {

        private final Function<String, String> encodeFunction;

        @Override
        public String encode(CharSequence rawPassword) {
            return encodeFunction.apply(rawPassword.toString());
        }

        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return Objects.equals(encode(rawPassword), encodedPassword);
        }
    }

}
